package com.lzp.systemopt.camera.impl

import android.Manifest
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.pm.PackageManager
import android.content.res.Configuration
import android.graphics.Matrix
import android.graphics.RectF
import android.graphics.SurfaceTexture
import android.hardware.camera2.*
import android.media.MediaRecorder
import android.os.Handler
import android.os.HandlerThread
import android.util.Size
import android.util.SparseIntArray
import android.view.Surface
import android.view.TextureView
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import com.lzp.systemopt.camera.CaptureListener
import com.lzp.systemopt.camera.CompareSizesByArea
import com.lzp.systemopt.camera.ICamera
import com.lzp.systemopt.camera.ICaptureVideo
import com.lzp.systemopt.camera.view.CameraSurfaceView
import com.utils.LogUtils
import com.utils.toast.ToastUtils
import java.util.*
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit

/**
 * @description:
 * @author: lxp
 * @date: 2022/1/27 下午3:03
 */
class CaptureVideoImpl() : ICamera, ICaptureVideo {

    private lateinit var mActivity: Activity
    private lateinit var mTextureView: CameraSurfaceView
    private lateinit var mCameraManager: CameraManager

    private var mBackgroundThread: HandlerThread? = null
    private var mBackgroundHandler: Handler? = null

    private var mCaptureListener: CaptureListener? = null

    // 相机对象
    private var mCameraDevice: CameraDevice? = null

    private var captureSession: CameraCaptureSession? = null

    private var sensorOrientation = 0
    private var mediaRecorder: MediaRecorder? = null

    private var mPreviewSize = Size(0, 0)
    private lateinit var videoSize: Size
    private var cameraDevice: CameraDevice? = null
    private val cameraOpenCloseLock = Semaphore(1)

    private lateinit var previewRequestBuilder: CaptureRequest.Builder

    private var nextVideoAbsolutePath: String? = null

    private val SENSOR_ORIENTATION_DEFAULT_DEGREES = 90
    private val SENSOR_ORIENTATION_INVERSE_DEGREES = 270
    private val DEFAULT_ORIENTATIONS = SparseIntArray().apply {
        append(Surface.ROTATION_0, 90)
        append(Surface.ROTATION_90, 0)
        append(Surface.ROTATION_180, 270)
        append(Surface.ROTATION_270, 180)
    }
    private val INVERSE_ORIENTATIONS = SparseIntArray().apply {
        append(Surface.ROTATION_0, 270)
        append(Surface.ROTATION_90, 180)
        append(Surface.ROTATION_180, 90)
        append(Surface.ROTATION_270, 0)
    }


    private val mSurfaceTextureListener = object : TextureView.SurfaceTextureListener {

        override fun onSurfaceTextureAvailable(texture: SurfaceTexture, width: Int, height: Int) {
            openCamera(width, height)
        }

        override fun onSurfaceTextureSizeChanged(texture: SurfaceTexture, width: Int, height: Int) {
            configureTransform(width, height)
        }

        override fun onSurfaceTextureDestroyed(surfaceTexture: SurfaceTexture) = true

        override fun onSurfaceTextureUpdated(surfaceTexture: SurfaceTexture) = Unit

    }

    private val stateCallback = object : CameraDevice.StateCallback() {

        override fun onOpened(cameraDevice: CameraDevice) {
            cameraOpenCloseLock.release()
            this@CaptureVideoImpl.cameraDevice = cameraDevice
            startPreview()
            configureTransform(mTextureView.width, mTextureView.height)
        }

        override fun onDisconnected(cameraDevice: CameraDevice) {
            cameraOpenCloseLock.release()
            cameraDevice.close()
            this@CaptureVideoImpl.cameraDevice = null
        }

        override fun onError(cameraDevice: CameraDevice, error: Int) {
            cameraOpenCloseLock.release()
            cameraDevice.close()
            this@CaptureVideoImpl.cameraDevice = null
        }
    }

    private fun startPreview() {
        if (cameraDevice == null || !mTextureView.isAvailable) return
        try {
            closePreviewSession()
            val texture = mTextureView.surfaceTexture!!
            texture.setDefaultBufferSize(mPreviewSize.width, mPreviewSize.height)
            previewRequestBuilder =
                cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW)

            val previewSurface = Surface(texture)
            previewRequestBuilder.addTarget(previewSurface)

            cameraDevice?.createCaptureSession(listOf(previewSurface),
                object : CameraCaptureSession.StateCallback() {
                    override fun onConfigured(session: CameraCaptureSession) {
                        captureSession = session
                        updatePreview()
                    }

                    override fun onConfigureFailed(session: CameraCaptureSession) {
                        ToastUtils.toastText("video preview fail.")
                    }
                }, mBackgroundHandler
            )
        } catch (e: CameraAccessException) {
            LogUtils.e("video preview fail.")
        }

    }

    private fun updatePreview() {
        if (cameraDevice == null) return
        try {
            setUpCaptureRequestBuilder(previewRequestBuilder)
            HandlerThread("CameraPreview").start()
            captureSession?.setRepeatingRequest(
                previewRequestBuilder.build(),
                null, mBackgroundHandler
            )
        } catch (e: CameraAccessException) {
        }
    }

    private fun setUpCaptureRequestBuilder(builder: CaptureRequest.Builder?) {
        builder?.set(CaptureRequest.CONTROL_MODE, CameraMetadata.CONTROL_MODE_AUTO)
    }

    private fun closePreviewSession() {
        captureSession?.close()
        captureSession = null
    }

    override fun initCamera(activity: Activity, cameraSurfaceView: CameraSurfaceView) {
        this.mActivity = activity
        this.mTextureView = cameraSurfaceView
        this.mCameraManager = activity.getSystemService(Context.CAMERA_SERVICE) as CameraManager
    }

    override fun startCaptureVideoPreview() {
        startBackgroundThread()
        if (mTextureView.isAvailable) {
            openCamera(mTextureView.width, mTextureView.height)
        } else {
            mTextureView.surfaceTextureListener = mSurfaceTextureListener
        }
    }

    @SuppressLint("MissingPermission")
    private fun openCamera(width: Int, height: Int) {
        mPreviewSize = Size(width, height)
        if (!hasVideoPermission()) {
            LogUtils.d("no permission")
            return
        }
        if (mActivity.isFinishing) {
            return
        }
        try {
            if (!cameraOpenCloseLock.tryAcquire(2500, TimeUnit.MILLISECONDS)) {
                ToastUtils.toastText("Time out waiting to lock camera opening.")
            }
            val characteristics = mCameraManager.getCameraCharacteristics(getCameraId().toString())
            val map = characteristics.get(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP)
            sensorOrientation = characteristics.get(CameraCharacteristics.SENSOR_ORIENTATION) ?: 0
            videoSize = chooseVideoSize(map?.getOutputSizes(MediaRecorder::class.java))
            mPreviewSize = chooseOptimalSize(
                map?.getOutputSizes(SurfaceTexture::class.java),
                width, height, videoSize
            )
            if (mActivity.resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
                mTextureView.setAspectRatio(mPreviewSize.width, mPreviewSize.height)
            } else {
                mTextureView.setAspectRatio(mPreviewSize.height, mPreviewSize.width)
            }
            configureTransform(width, height)
            mediaRecorder = MediaRecorder()
            mCameraManager.openCamera(getCameraId().toString(), stateCallback, null)
        } catch (e: Exception) {
            LogUtils.e("open video preview fail.")
        }
    }

    private fun configureTransform(viewWidth: Int, viewHeight: Int) {
        val rotation = (mActivity as FragmentActivity).windowManager.defaultDisplay.rotation
        val matrix = Matrix()
        val viewRect = RectF(0f, 0f, viewWidth.toFloat(), viewHeight.toFloat())
        val bufferRect = RectF(0f, 0f, mPreviewSize.height.toFloat(), mPreviewSize.width.toFloat())
        val centerX = viewRect.centerX()
        val centerY = viewRect.centerY()

        if (Surface.ROTATION_90 == rotation || Surface.ROTATION_270 == rotation) {
            bufferRect.offset(centerX - bufferRect.centerX(), centerY - bufferRect.centerY())
            matrix.setRectToRect(viewRect, bufferRect, Matrix.ScaleToFit.FILL)
            val scale = Math.max(
                viewHeight.toFloat() / mPreviewSize.height,
                viewWidth.toFloat() / mPreviewSize.width
            )
            with(matrix) {
                postScale(scale, scale, centerX, centerY)
                postRotate((90 * (rotation - 2)).toFloat(), centerX, centerY)
            }
        }
        mTextureView.setTransform(matrix)
    }

    private fun chooseVideoSize(choices: Array<Size>?): Size {
        if (choices == null) {
            return Size(0, 0)
        }
        return choices.firstOrNull {
            it.width == it.height * 4 / 3 && it.width <= 1080
        } ?: choices[choices.size - 1]
    }

    private fun chooseOptimalSize(
        choices: Array<Size>?,
        width: Int,
        height: Int,
        aspectRatio: Size
    ): Size {
        if (choices == null) {
            return Size(0, 0)
        }
        val w = aspectRatio.width
        val h = aspectRatio.height
        val bigEnough = choices.filter {
            it.height == it.width * h / w && it.width >= width && it.height >= height
        }
        return if (bigEnough.isNotEmpty()) {
            Collections.min(bigEnough, CompareSizesByArea())
        } else {
            choices[0]
        }
    }

    private fun hasVideoPermission(): Boolean {
        val permissionCamera =
            ContextCompat.checkSelfPermission(mActivity, Manifest.permission.CAMERA)
        if (permissionCamera != PackageManager.PERMISSION_GRANTED) {
            return false
        }
        val permissionAudio =
            ContextCompat.checkSelfPermission(mActivity, Manifest.permission.RECORD_AUDIO)
        if (permissionAudio != PackageManager.PERMISSION_GRANTED) {
            return false
        }
        return true
    }

    override fun stopCaptureVideoPreview() {
        closePreviewSession()
        releaseCamera("stop capture video preview")
    }

    override fun captureVideo() {
        if (cameraDevice == null || !mTextureView.isAvailable) return
        try {
            closePreviewSession()
            setUpMediaRecorder()
            val texture = mTextureView.surfaceTexture?.apply {
                setDefaultBufferSize(mPreviewSize.width, mPreviewSize.height)
            }
            val previewSurface = Surface(texture)
            val recorderSurface = mediaRecorder!!.surface
            val surfaces = ArrayList<Surface>().apply {
                add(previewSurface)
                add(recorderSurface)
            }
            previewRequestBuilder =
                cameraDevice!!.createCaptureRequest(CameraDevice.TEMPLATE_RECORD).apply {
                    addTarget(previewSurface)
                    addTarget(recorderSurface)
                }
            cameraDevice?.createCaptureSession(surfaces,
                object : CameraCaptureSession.StateCallback() {

                    override fun onConfigured(cameraCaptureSession: CameraCaptureSession) {
                        captureSession = cameraCaptureSession
                        updatePreview()
                        mediaRecorder?.start()
                        mCaptureListener?.onStartCapture()
                    }

                    override fun onConfigureFailed(cameraCaptureSession: CameraCaptureSession) {
                        ToastUtils.toastText("capture video fail.")
                    }
                }, mBackgroundHandler
            )
        } catch (e: Exception) {
            LogUtils.e("start capture video fail.")
        }
    }

    private fun setUpMediaRecorder() {
        if (nextVideoAbsolutePath.isNullOrEmpty()) {
            nextVideoAbsolutePath =
                com.lzp.systemopt.camera.CameraManager.getInstance().generateVideoPath()
        }

        val rotation = mActivity.windowManager.defaultDisplay.rotation
        when (sensorOrientation) {
            SENSOR_ORIENTATION_DEFAULT_DEGREES ->
                mediaRecorder?.setOrientationHint(DEFAULT_ORIENTATIONS.get(rotation))
            SENSOR_ORIENTATION_INVERSE_DEGREES ->
                mediaRecorder?.setOrientationHint(INVERSE_ORIENTATIONS.get(rotation))
        }

        mediaRecorder?.apply {
            setAudioSource(MediaRecorder.AudioSource.MIC)
            setVideoSource(MediaRecorder.VideoSource.SURFACE)
            setOutputFormat(MediaRecorder.OutputFormat.MPEG_4)
            setOutputFile(nextVideoAbsolutePath)
            setVideoEncodingBitRate(10000000)
            setVideoFrameRate(30)
            setVideoSize(videoSize.width, videoSize.height)
            setVideoEncoder(MediaRecorder.VideoEncoder.H264)
            setAudioEncoder(MediaRecorder.AudioEncoder.AAC)
            prepare()
        }
    }

    override fun stopCaptureVideo() {
        mediaRecorder?.apply {
            stop()
            reset()
        }
        LogUtils.d("stopCaptureVideo: $nextVideoAbsolutePath")
        nextVideoAbsolutePath = null
        startPreview()
    }

    override fun setCaptureListener(listener: CaptureListener?) {
        this.mCaptureListener = listener
    }

    override fun requestSwitchCamera() {
        releaseCamera("switch cameraId")
        openCamera(mPreviewSize.width, mPreviewSize.height)
    }

    override fun releaseCamera(reason: String) {
        try {
            cameraOpenCloseLock.acquire()
            closePreviewSession()
            cameraDevice?.close()
            cameraDevice = null
            mediaRecorder?.release()
            mediaRecorder = null
            mBackgroundThread?.quitSafely()
            mBackgroundThread?.join()
            mBackgroundThread = null
            mBackgroundHandler = null
        } catch (e: InterruptedException) {
            LogUtils.e("Interrupted while trying to lock camera closing.")
        } finally {
            cameraOpenCloseLock.release()
        }
    }

    private fun startBackgroundThread() {
        val handlerThread = HandlerThread("cameraBackground")
        handlerThread.start()
        mBackgroundThread = handlerThread
        mBackgroundHandler = Handler(handlerThread.looper)
    }

    private fun getCameraId(): Int {
        return com.lzp.systemopt.camera.CameraManager.mCameraId
    }

}